package net;

import java.io.ObjectOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import core.Main;
import core.Servent;

import util.Debug;

/**
 * Listens for connections on PORT. There can only exist one Deamon thread in 
 * the system because of the static access to the availableServerSockets list.
 * 
 * @author Emil Wall
 * @version 0.1
 */
public class Daemon extends Thread {

	// Public (static) final fields
	public static final int DEFAULT_PORT  = 38001;
	// Private static fields
	private static final int SLEEP_MS = 1000;
	
	private final int PORT;
	
	// Private non-static fields
	private boolean terminate = false;
	private boolean shouldWait = false;
	private ServerSocket daemonServerSocket = null;
	private Socket daemonSocket = null;
	private ObjectOutputStream os = null;
//	private LinkedList<Connection> connections = new LinkedList<Connection>();
	private String name;
	private Main master;
	private Servent servent;
	
	/**
	 * Constructor. Binds the SocketServer to PORT and initializes list of 
	 * available ServerSockets.
	 * @param myPort port to listen to
	 * @param name of the Daemon
	 * @param master The JFrame object that "owns" this Daemon
	 * @throws IOException if port is already in use
	 */
	public Daemon(int myPort, String name, Main master, Servent servent) throws IOException {
		this.servent = servent;
		this.master = master;
		this.name = name;
		PORT = myPort;
		daemonServerSocket = new ServerSocket(PORT);
		master.writeToLog(name,"Daemon thread started: Port "+PORT);
	}
	
	/**
	 * Daemon thread. Loops forever to listen for connections on PORT.
	 */
	public void run() {
		while(!terminate) {
			if(!shouldWait && daemonServerSocket.isBound()) {
				listenForConnection();
			} else {
				try {
					Thread.sleep(SLEEP_MS);
					shouldWait = false;
				} catch (InterruptedException e) {
					if(Debug.verbose) {
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	/**
	 * Waits for a connection on PORT, creates a new ServerSocket and send its 
	 * port number to the connecting host and passes the ServerSocket to a new 
	 * Connection object which is run as a separate thread. Then disconnects.
	 */
	private void listenForConnection() {
		try {
			daemonSocket = daemonServerSocket.accept();
			
			os = new ObjectOutputStream(daemonSocket.getOutputStream());
			int temp = servent.addConnection();
			master.writeToLog(name,"Writing to os: "+temp);
			os.writeInt(temp);
			os.flush();
		} catch (IOException e) {
			shouldWait = true;
			if(!terminate && Debug.verbose) {
				e.printStackTrace();
			}
		} catch (IllegalThreadStateException e) {
			if(Debug.verbose) {
				e.printStackTrace();
			}
		} finally {
			reset();
		}
	}
	
	/**
	 * Closes the socket and I/O streams and assigns them a null value
	 */
	private void reset() {
		try {
			if(os != null) {
				os.close();
				os = null;
			}
			if(daemonSocket != null) {
				daemonSocket.close();
				daemonSocket = null;
			}
		} catch (IOException e) {
			// Ignore close errors
		}
	}
	
	/**
	 * Disconnects all sockets
	 */
	public void cleanup() {
		master.writeToLog(name,"Daemon thread terminating...");
		terminate = true;
		reset();
		try {
			daemonServerSocket.close();
			daemonServerSocket = null;
		} catch (IOException e) {
			if(Debug.verbose) {
				e.printStackTrace();
			}
		}
	}
	
}
